import logging
from datetime import timedelta
from typing import List

from celery import shared_task
from django.db import transaction
from django.utils import timezone

from orders.models import Order
from payments.models import PaymentRecord


logger = logging.getLogger("django")


@shared_task(name="orders.cancel_if_unpaid", autoretry_for=(Exception,), retry_backoff=True, retry_jitter=True, max_retries=3)
def cancel_if_unpaid(order_number: str) -> bool:
    """在超时点检查并取消未支付订单。

    幂等：仅当订单仍为未支付(0)时，才更新为超时取消(3)。
    返回：是否发生了状态更新。
    """
    with transaction.atomic():
        updated = (
            Order.objects
            .select_for_update(skip_locked=True)
            .filter(order_number=order_number, order_status=0)
            .update(order_status=3)
        )

        if not updated:
            logger.info("cancel_if_unpaid: no-op, order already not unpaid, order_number=%s", order_number)
            return False

        # 同步更新支付记录：仅将未支付(0)标记为超时取消(3)
        pr_updated = (
            PaymentRecord.objects
            .filter(order__order_number=order_number, pay_status=0)
            .update(pay_status=3, pay_remark="超时取消")
        )

        logger.info(
            "cancel_if_unpaid: order canceled due to timeout, order_number=%s, payment_records_updated=%s",
            order_number,
            pr_updated,
        )
        return True


@shared_task(name="orders.sweep_unpaid_orders", autoretry_for=(Exception,), retry_backoff=True, retry_jitter=True, max_retries=3)
def sweep_unpaid_orders(cutoff_minutes: int = 30, batch_size: int = 500) -> int:
    """兜底扫描：将超过 cutoff 的未支付订单置为超时取消。

    返回：本次置为超时取消的订单数量（可能小于等于 batch_size）。
    """
    cutoff_at = timezone.now() - timedelta(minutes=int(cutoff_minutes))

    # 取一批候选订单号
    candidates: List[str] = list(
        Order.objects
        .filter(order_status=0, order_time__lte=cutoff_at)
        .order_by("id")
        .values_list("order_number", flat=True)[: int(batch_size)]
    )

    if not candidates:
        logger.info("sweep_unpaid_orders: no candidates found")
        return 0

    with transaction.atomic():
        updated = (
            Order.objects
            .filter(order_status=0, order_number__in=candidates)
            .update(order_status=3)
        )
        if updated:
            pr_updated = (
                PaymentRecord.objects
                .filter(order__order_number__in=candidates, pay_status=0)
                .update(pay_status=3, pay_remark="超时取消")
            )
        else:
            pr_updated = 0

    logger.info(
        "sweep_unpaid_orders: cutoff=%s, batch=%s, orders_canceled=%s, payment_records_updated=%s",
        cutoff_minutes,
        batch_size,
        updated,
        pr_updated,
    )
    return int(updated)

